package com.d3works.apps.account.service.payment.invoice.payment;

import com.d3works.apps.account.db.Account;
import com.d3works.apps.account.db.AccountConfig;
import com.d3works.apps.account.db.AccountManagement;
import com.d3works.apps.account.db.Invoice;
import com.d3works.apps.account.db.InvoicePayment;
import com.d3works.apps.account.db.InvoiceTermPayment;
import com.d3works.apps.account.db.Journal;
import com.d3works.apps.account.db.Move;
import com.d3works.apps.account.db.MoveLine;
import com.d3works.apps.account.db.PaymentMode;
import com.d3works.apps.account.db.Reconcile;
import com.d3works.apps.account.db.Tax;
import com.d3works.apps.account.db.repo.FinancialDiscountRepository;
import com.d3works.apps.account.db.repo.InvoicePaymentRepository;
import com.d3works.apps.account.db.repo.InvoiceRepository;
import com.d3works.apps.account.db.repo.MoveRepository;
import com.d3works.apps.account.db.repo.PaymentModeRepository;
import com.d3works.apps.account.exception.AccountExceptionMessage;
import com.d3works.apps.account.service.AccountManagementAccountService;
import com.d3works.apps.account.service.AccountingSituationService;
import com.d3works.apps.account.service.ReconcileService;
import com.d3works.apps.account.service.app.AppAccountService;
import com.d3works.apps.account.service.config.AccountConfigService;
import com.d3works.apps.account.service.invoice.InvoiceToolService;
import com.d3works.apps.account.service.move.MoveCreateService;
import com.d3works.apps.account.service.move.MoveToolService;
import com.d3works.apps.account.service.move.MoveValidateService;
import com.d3works.apps.account.service.moveline.MoveLineCreateService;
import com.d3works.apps.account.service.payment.PaymentModeService;
import com.d3works.apps.base.db.BankDetails;
import com.d3works.apps.base.db.Company;
import com.d3works.apps.base.db.Partner;
import com.d3works.exception.D3worksException;
import com.d3works.exception.db.repo.TraceBackRepository;
import com.d3works.i18n.I18n;
import com.d3works.inject.Beans;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
import com.google.inject.servlet.RequestScoped;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import javax.xml.bind.JAXBException;
import javax.xml.datatype.DatatypeConfigurationException;
import org.apache.commons.collections.CollectionUtils;

@RequestScoped
public class InvoicePaymentValidateServiceImpl implements InvoicePaymentValidateService {

  protected PaymentModeService paymentModeService;
  protected MoveCreateService moveCreateService;
  protected MoveValidateService moveValidateService;
  protected MoveToolService moveToolService;
  protected MoveLineCreateService moveLineCreateService;
  protected AccountConfigService accountConfigService;
  protected InvoicePaymentRepository invoicePaymentRepository;
  protected ReconcileService reconcileService;
  protected AppAccountService appAccountService;
  protected AccountManagementAccountService accountManagementAccountService;

  @Inject
  public InvoicePaymentValidateServiceImpl(
      PaymentModeService paymentModeService,
      MoveCreateService moveCreateService,
      MoveValidateService moveValidateService,
      MoveToolService moveToolService,
      MoveLineCreateService moveLineCreateService,
      AccountConfigService accountConfigService,
      InvoicePaymentRepository invoicePaymentRepository,
      ReconcileService reconcileService,
      AppAccountService appAccountService,
      AccountManagementAccountService accountManagementAccountService) {

    this.paymentModeService = paymentModeService;
    this.moveLineCreateService = moveLineCreateService;
    this.moveCreateService = moveCreateService;
    this.moveValidateService = moveValidateService;
    this.moveToolService = moveToolService;
    this.accountConfigService = accountConfigService;
    this.invoicePaymentRepository = invoicePaymentRepository;
    this.reconcileService = reconcileService;
    this.appAccountService = appAccountService;
    this.accountManagementAccountService = accountManagementAccountService;
  }

  /**
   * Method to validate an invoice Payment
   *
   * <p>Create the eventual move (depending general configuration) and reconcile it with the invoice
   * move Compute the amount paid on invoice Change the status to validated
   *
   * @param invoicePayment An invoice payment
   * @throws D3worksException
   * @throws DatatypeConfigurationException
   * @throws IOException
   * @throws JAXBException
   */
  @Override
  @Transactional(rollbackOn = {Exception.class})
  public void validate(InvoicePayment invoicePayment, boolean force)
      throws D3worksException, JAXBException, IOException, DatatypeConfigurationException {

    Invoice invoice = invoicePayment.getInvoice();
    validatePartnerAccount(invoice);
    invoice.setNextDueDate(Beans.get(InvoiceToolService.class).getNextDueDate(invoice));

    if (!force && invoicePayment.getStatusSelect() != InvoicePaymentRepository.STATUS_DRAFT) {
      return;
    }

    setInvoicePaymentStatus(invoicePayment);
    createInvoicePaymentMove(invoicePayment);

    if (invoicePayment.getInvoice() != null
        && invoicePayment.getInvoice().getOperationSubTypeSelect()
            == InvoiceRepository.OPERATION_SUB_TYPE_ADVANCE) {
      invoicePayment.setTypeSelect(InvoicePaymentRepository.TYPE_ADVANCEPAYMENT);
    }
    invoicePaymentRepository.save(invoicePayment);
  }

  protected void validatePartnerAccount(Invoice invoice) throws D3worksException {
    Account partnerAccount = invoice.getPartnerAccount();
    if (!partnerAccount.getReconcileOk() || !partnerAccount.getUseForPartnerBalance()) {
      throw new D3worksException(
          TraceBackRepository.CATEGORY_CONFIGURATION_ERROR,
          I18n.get(AccountExceptionMessage.ACCOUNT_RECONCILABLE_USE_FOR_PARTNER_BALANCE));
    }
  }

  protected void createInvoicePaymentMove(InvoicePayment invoicePayment)
      throws D3worksException, JAXBException, IOException, DatatypeConfigurationException {
    Invoice invoice = invoicePayment.getInvoice();
    if (accountConfigService
        .getAccountConfig(invoice.getCompany())
        .getGenerateMoveForInvoicePayment()) {
      this.createMoveForInvoicePayment(invoicePayment);
    } else {
      Beans.get(AccountingSituationService.class).updateCustomerCredit(invoice.getPartner());
      invoicePaymentRepository.save(invoicePayment);
    }
  }

  protected void setInvoicePaymentStatus(InvoicePayment invoicePayment) throws D3worksException {
    invoicePayment.setStatusSelect(InvoicePaymentRepository.STATUS_VALIDATED);
  }

  @Override
  @Transactional(rollbackOn = {Exception.class})
  public void validate(InvoicePayment invoicePayment)
      throws D3worksException, JAXBException, IOException, DatatypeConfigurationException {
    validate(invoicePayment, false);
  }

  /**
   * Method to create a payment move for an invoice Payment
   *
   * <p>Create a move and reconcile it with the invoice move
   *
   * @param invoicePayment An invoice payment
   * @throws D3worksException
   */
  @Transactional(rollbackOn = {Exception.class})
  public InvoicePayment createMoveForInvoicePayment(InvoicePayment invoicePayment)
      throws D3worksException {

    Invoice invoice = invoicePayment.getInvoice();
    Company company = invoice.getCompany();
    PaymentMode paymentMode = invoicePayment.getPaymentMode();
    Partner partner = invoice.getPartner();
    LocalDate paymentDate = invoicePayment.getPaymentDate();
    BankDetails companyBankDetails = invoicePayment.getCompanyBankDetails();
    String description = invoicePayment.getDescription();
    if (description == null || description.isEmpty()) {
      description =
          String.format(
              "%s-%s-%s",
              invoicePayment.getPaymentMode().getName(),
              invoice.getPartner().getName(),
              invoice.getDueDate().format(DateTimeFormatter.ofPattern("dd/MM/yyyy")));
    }

    Account customerAccount;

    Journal journal =
        paymentModeService.getPaymentModeJournal(paymentMode, company, companyBankDetails);

    List<MoveLine> invoiceMoveLines = moveToolService.getInvoiceCustomerMoveLines(invoicePayment);

    if (invoice.getOperationSubTypeSelect() == InvoiceRepository.OPERATION_SUB_TYPE_ADVANCE) {

      AccountConfig accountConfig = accountConfigService.getAccountConfig(company);

      if (InvoiceToolService.isPurchase(invoice)) {
        customerAccount = accountConfigService.getSupplierAdvancePaymentAccount(accountConfig);
      } else {
        customerAccount = accountConfigService.getAdvancePaymentAccount(accountConfig);
      }

    } else {
      if (CollectionUtils.isEmpty(invoiceMoveLines)) {
        return null;
      }
      customerAccount = invoiceMoveLines.get(0).getAccount();
    }

    Move move =
        moveCreateService.createMove(
            journal,
            company,
            invoicePayment.getCurrency(),
            partner,
            paymentDate,
            paymentDate,
            paymentMode,
            invoice.getFiscalPosition(),
            invoice.getBankDetails(),
            MoveRepository.TECHNICAL_ORIGIN_AUTOMATIC,
            MoveRepository.FUNCTIONAL_ORIGIN_PAYMENT,
            getOriginFromInvoicePayment(invoicePayment),
            description,
            invoice.getCompanyBankDetails());

    MoveLine customerMoveLine = null;
    move.setTradingName(invoice.getTradingName());

    BigDecimal maxAmount =
        invoiceMoveLines.stream()
            .map(MoveLine::getAmountRemaining)
            .reduce(BigDecimal::add)
            .orElse(BigDecimal.ZERO);
    move = this.fillMove(invoicePayment, move, customerAccount, maxAmount);
    moveValidateService.accounting(move);

    for (MoveLine moveline : move.getMoveLineList()) {
      if (moveline.getAccount() == customerAccount) {
        customerMoveLine = moveline;
      }
    }

    invoicePayment.setMove(move);
    if (customerMoveLine != null
        && invoice.getOperationSubTypeSelect() != InvoiceRepository.OPERATION_SUB_TYPE_ADVANCE) {
      for (MoveLine invoiceMoveLine : invoiceMoveLines) {
        Reconcile reconcile =
            reconcileService.reconcile(
                invoiceMoveLine, customerMoveLine, true, true, invoicePayment);

        if (reconcile == null) {
          throw new D3worksException(
              TraceBackRepository.CATEGORY_INCONSISTENCY,
              I18n.get(AccountExceptionMessage.INVOICE_PAYMENT_CANNOT_RECONCILE),
              invoiceMoveLine.getName(),
              invoiceMoveLine.getAccount().getCode(),
              customerMoveLine.getName(),
              customerMoveLine.getAccount().getCode());
        }

        invoicePayment.addReconcileListItem(reconcile);
      }
    }

    invoicePaymentRepository.save(invoicePayment);
    return invoicePayment;
  }

  public String getOriginFromInvoicePayment(InvoicePayment invoicePayment) {
    String origin = invoicePayment.getInvoice().getInvoiceId();
    if (invoicePayment.getPaymentMode().getTypeSelect() == PaymentModeRepository.TYPE_CHEQUE
        || invoicePayment.getPaymentMode().getTypeSelect()
            == PaymentModeRepository.TYPE_IPO_CHEQUE) {
      origin = invoicePayment.getChequeNumber() != null ? invoicePayment.getChequeNumber() : origin;
    } else if (invoicePayment.getPaymentMode().getTypeSelect()
        == PaymentModeRepository.TYPE_BANK_CARD) {
      origin =
          invoicePayment.getInvoicePaymentRef() != null
              ? invoicePayment.getInvoicePaymentRef()
              : origin;
    }
    if (invoicePayment.getInvoice().getOperationTypeSelect()
            == InvoiceRepository.OPERATION_TYPE_SUPPLIER_PURCHASE
        || invoicePayment.getInvoice().getOperationTypeSelect()
            == InvoiceRepository.OPERATION_TYPE_SUPPLIER_REFUND) {
      origin = invoicePayment.getInvoice().getSupplierInvoiceNb();
    }
    return origin;
  }

  @Transactional(rollbackOn = {Exception.class})
  public Move fillMove(
      InvoicePayment invoicePayment, Move move, Account customerAccount, BigDecimal maxAmount)
      throws D3worksException {

    Invoice invoice = invoicePayment.getInvoice();
    Company company = invoice.getCompany();
    BigDecimal paymentAmount = invoicePayment.getAmount();
    PaymentMode paymentMode = invoicePayment.getPaymentMode();
    Partner partner = invoice.getPartner();
    LocalDate paymentDate = invoicePayment.getPaymentDate();
    BankDetails companyBankDetails = invoicePayment.getCompanyBankDetails();
    boolean isDebitInvoice = moveToolService.isDebitCustomer(invoice, true);
    String origin = getOriginFromInvoicePayment(invoicePayment);
    boolean isFinancialDiscount = this.isFinancialDiscount(invoicePayment);
    int counter = 1;
    boolean financialDiscountVat =
        invoicePayment.getFinancialDiscount() != null
            && invoicePayment.getFinancialDiscount().getDiscountBaseSelect()
                == FinancialDiscountRepository.DISCOUNT_BASE_VAT;
    boolean isPurchase = InvoiceToolService.isPurchase(invoice);

    AccountConfig accountConfig = accountConfigService.getAccountConfig(company);

    BigDecimal companyPaymentAmount =
        invoicePayment.getInvoiceTermPaymentList().stream()
            .map(InvoiceTermPayment::getCompanyPaidAmount)
            .reduce(BigDecimal::add)
            .orElse(BigDecimal.ZERO)
            .min(maxAmount);
    BigDecimal currencyRate = companyPaymentAmount.divide(paymentAmount, 5, RoundingMode.HALF_UP);

    move.addMoveLineListItem(
        moveLineCreateService.createMoveLine(
            move,
            partner,
            paymentModeService.getPaymentModeAccount(paymentMode, company, companyBankDetails),
            paymentAmount,
            companyPaymentAmount,
            currencyRate,
            isDebitInvoice,
            paymentDate,
            null,
            paymentDate,
            counter++,
            origin,
            move.getDescription()));
    MoveLine financialDiscountMoveLine = null;
    if (isFinancialDiscount) {
      financialDiscountMoveLine =
          moveLineCreateService.createMoveLine(
              move,
              partner,
              this.getFinancialDiscountAccount(invoice, company),
              invoicePayment.getFinancialDiscountAmount(),
              isDebitInvoice,
              paymentDate,
              null,
              counter++,
              origin,
              invoicePayment.getDescription());

      Tax financialDiscountTax = null;
      if (financialDiscountVat) {
        financialDiscountTax =
            isPurchase
                ? accountConfigService.getPurchFinancialDiscountTax(accountConfig)
                : accountConfigService.getSaleFinancialDiscountTax(accountConfig);

        if (financialDiscountTax.getActiveTaxLine() != null) {
          financialDiscountMoveLine.setTaxLine(financialDiscountTax.getActiveTaxLine());
          financialDiscountMoveLine.setTaxRate(financialDiscountTax.getActiveTaxLine().getValue());
          financialDiscountMoveLine.setTaxCode(financialDiscountTax.getCode());
        }
      }

      move.addMoveLineListItem(financialDiscountMoveLine);

      paymentAmount =
          invoicePayment
              .getAmount()
              .add(
                  invoicePayment
                      .getFinancialDiscountAmount()
                      .add(invoicePayment.getFinancialDiscountTaxAmount()));
      companyPaymentAmount = paymentAmount;
    }

    move.addMoveLineListItem(
        moveLineCreateService.createMoveLine(
            move,
            partner,
            customerAccount,
            paymentAmount,
            companyPaymentAmount,
            currencyRate,
            !isDebitInvoice,
            paymentDate,
            null,
            paymentDate,
            counter++,
            origin,
            move.getDescription()));

    if (isFinancialDiscount
        && invoicePayment != null
        && invoicePayment.getFinancialDiscount() != null
        && financialDiscountVat
        && financialDiscountMoveLine != null
        && BigDecimal.ZERO.compareTo(invoicePayment.getFinancialDiscountTaxAmount()) != 0) {
      int vatSytem = financialDiscountMoveLine.getAccount().getVatSystemSelect();
      Account financialDiscountVATAccount =
          this.getFinancialDiscountVATAccount(
              invoice, company, move.getJournal(), vatSytem, move.getFunctionalOriginSelect());

      if (financialDiscountVATAccount != null) {
        MoveLine financialDiscountVatMoveLine =
            moveLineCreateService.createMoveLine(
                move,
                partner,
                financialDiscountVATAccount,
                invoicePayment.getFinancialDiscountTaxAmount(),
                isDebitInvoice,
                paymentDate,
                null,
                counter,
                origin,
                invoicePayment.getDescription());
        financialDiscountVatMoveLine.setTaxLine(financialDiscountMoveLine.getTaxLine());
        financialDiscountVatMoveLine.setTaxRate(financialDiscountMoveLine.getTaxRate());
        financialDiscountVatMoveLine.setTaxCode(financialDiscountMoveLine.getTaxCode());
        financialDiscountVatMoveLine.setVatSystemSelect(vatSytem);
        move.addMoveLineListItem(financialDiscountVatMoveLine);
      }
    }
    return move;
  }

  protected boolean isFinancialDiscount(InvoicePayment invoicePayment) {
    return invoicePayment.getApplyFinancialDiscount()
        && invoicePayment.getFinancialDiscount() != null
        && appAccountService.getAppAccount().getManageFinancialDiscount();
  }

  protected Account getFinancialDiscountAccount(Invoice invoice, Company company)
      throws D3worksException {
    AccountConfig accountConfig = accountConfigService.getAccountConfig(company);
    if (InvoiceToolService.isPurchase(invoice)) {
      return accountConfigService.getPurchFinancialDiscountAccount(accountConfig);
    } else {
      return accountConfigService.getSaleFinancialDiscountAccount(accountConfig);
    }
  }

  protected Account getFinancialDiscountVATAccount(
      Invoice invoice, Company company, Journal journal, int vatSystemSelect, int functionalOrigin)
      throws D3worksException {
    AccountConfig accountConfig = accountConfigService.getAccountConfig(company);
    Tax tax =
        InvoiceToolService.isPurchase(invoice)
            ? accountConfigService.getPurchFinancialDiscountTax(accountConfig)
            : accountConfigService.getSaleFinancialDiscountTax(accountConfig);
    AccountManagement accountManagement =
        tax.getAccountManagementList().stream()
            .filter(it -> it.getCompany().equals(company))
            .findFirst()
            .orElse(null);
    return accountManagementAccountService.getTaxAccount(
        accountManagement, tax, company, journal, vatSystemSelect, functionalOrigin, false, true);
  }
}
